package com.syp.auth.service.business.impl;

import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
import java.util.Calendar;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;

import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.syp.auth.common.Constants;
import com.syp.auth.common.IVC;
import com.syp.auth.dao.po.AppEntity;
import com.syp.auth.dao.po.AppLogEntity;
import com.syp.auth.dao.po.ResourceEntity;
import com.syp.auth.dao.po.RoleResourceEntity;
import com.syp.auth.dao.po.UserEntity;
import com.syp.auth.dao.po.UserRoleEntity;
import com.syp.auth.dao.repository.AppLogRepository;
import com.syp.auth.dao.repository.AppRepository;
import com.syp.auth.dao.repository.ResourceRepository;
import com.syp.auth.dao.repository.RoleResourceRepository;
import com.syp.auth.dao.repository.UserRepository;
import com.syp.auth.dao.repository.UserRoleRepository;
import com.syp.auth.domain.enums.ResourceIsDefaultEnum;
import com.syp.auth.domain.vo.ResourceVo;
import com.syp.auth.export.common.GeneralAccessVo;
import com.syp.auth.export.common.GeneralUserVo;
import com.syp.auth.export.common.GorgeousParameters;
import com.syp.auth.export.common.TokenSecurity;
import com.syp.auth.export.request.AppLogRequest;
import com.syp.auth.export.request.TokenVerifyRequest;
import com.syp.auth.export.response.AppLogResponse;
import com.syp.auth.service.business.AuthorizeServiceI;
import com.syp.auth.service.business.NoSigninAuthenticationException;
import com.syp.auth.service.business.UsernameOrPasswordUncheckException;
import com.syp.auth.common.MD5Util;

@Service
public class AuthorizeServiceImpl implements AuthorizeServiceI {
	
	private static final Logger log = LoggerFactory.getLogger(AuthorizeServiceImpl.class);

	@Autowired
	private UserRepository userRepository;
	@Autowired
	private UserRoleRepository userRoleRepository;
	@Autowired
	private ResourceRepository resourceRepository;
	@Autowired
	private RoleResourceRepository roleResourceRepository;
	@Autowired
	private AppRepository appRepository;
	@Autowired
	private AppLogRepository appLogRepository;
	@Autowired
	private StringRedisTemplate stringRedisTemplate;

	@Override
	public void dsignin(GeneralUserVo guser, GeneralAccessVo gaccess, HttpServletResponse httpResponse) throws UsernameOrPasswordUncheckException, NoSigninAuthenticationException {
		UserEntity userEntity = this.userRepository.signin(guser.getUserCode(), MD5Util.md5(guser.getPassword()));
		if (userEntity == null) {
			log.info("用户名密码错误", guser.getUserCode(), gaccess.getApp());
			throw new UsernameOrPasswordUncheckException();
		}

		BeanUtils.copyProperties(userEntity, guser, new String[] { "password" });

		if (userEntity.getId() == Constants.SUPER_MAN_NO) {
			List<ResourceEntity> rs = this.resourceRepository.getByAppCode(gaccess.getApp());
			for (ResourceEntity resourceEntity : rs) {
				ResourceVo resource = this.transform(resourceEntity);

				guser.getPermitResList().add(resource);
				if (resource.getResUrl() != null && !resource.getResUrl().isEmpty()) {
					guser.getPermitUrlList().add(resourceEntity.getResUrl());
				}
			}
		} else {
			List<UserRoleEntity> urs = this.userRoleRepository.getByUserId(userEntity.getId());
			if (urs != null && !urs.isEmpty()) {
				List<Long> roleIdList = new LinkedList<>();
				for (UserRoleEntity ur : urs) {
					roleIdList.add(ur.getRoleId());
				}
				List<RoleResourceEntity> roleResourceEntityList = this.roleResourceRepository.getByRoleIdList(roleIdList);

				List<Long> resIdList = new LinkedList<>();
				for (RoleResourceEntity rrs : roleResourceEntityList) {
					resIdList.add(rrs.getResId());
				}

				List<ResourceEntity> rs = this.resourceRepository.getByAppCodeLimited(gaccess.getApp(), resIdList);
				for (ResourceEntity resourceEntity : rs) {
					ResourceVo resource = this.transform(resourceEntity);

					guser.getPermitResList().add(resource);
					if (resource.getResUrl() != null && !resource.getResUrl().isEmpty()) {
						guser.getPermitUrlList().add(resourceEntity.getResUrl());
					}
				}
			}
		}

		guser.setApp(gaccess.getApp());
		guser.setIsSuperMan(Constants.isSuperMan(guser.getId()));
		if (!Constants.isSuperMan(guser.getId())) {// 超管不控制访问权限
			long indexResCount = 0;
			for (ResourceVo res : guser.getPermitResList()) {
				if (res.getIsDefault() == ResourceIsDefaultEnum.TRUE.getCode()) {
					indexResCount++;
				}
			}

			if (indexResCount == 0) {
				log.info("用户：[{}]没有登录系统：[{}]的权限，请联系管理员", guser.getUserCode(), gaccess.getApp());
				throw new NoSigninAuthenticationException();
			}
		}
		AppEntity app = this.appRepository.getByAppCode(gaccess.getApp());
		String token = TokenSecurity.getInstunce().decrypt(gaccess.getSid());
		final String tokenKey = GorgeousParameters.SESSION_ID_PREFIX + token;
		final String tokenValue = JSON.toJSONString(guser);
		final int timeout = app.getSessionKeepMins() == null ? GorgeousParameters.TOKEN_EXISTS_MINUTES : app.getSessionKeepMins();
		this.stringRedisTemplate.opsForValue().set(tokenKey, tokenValue, timeout, TimeUnit.MINUTES);
	}

	private ResourceVo transform(ResourceEntity r) {
		ResourceVo resource = new ResourceVo();

		resource.setId(r.getId());
		resource.setResCode(r.getResCode());
		resource.setResName(r.getResName());
		resource.setResUrl(r.getResUrl());
		resource.setResType(r.getResType());
		resource.setResSeq(r.getResSeq());
		resource.setIsDefault(r.getIsDefault());
		resource.setIsLogAccess(r.getIsLogAccess());

		return resource;
	}

	@Override
	public boolean verify(TokenVerifyRequest request) {
		String token = TokenSecurity.getInstunce().decrypt(request.getSid());
		final String tokenKey = GorgeousParameters.SESSION_ID_PREFIX + token;
		final int timeout = request.getTimoutMins() == null ? GorgeousParameters.TOKEN_EXISTS_MINUTES : request.getTimoutMins();
		return this.stringRedisTemplate.execute(new RedisCallback<Boolean>() {
			@Override
			public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
				try {
					boolean exists = connection.exists(tokenKey.getBytes(GorgeousParameters.CHARSET_UTF8));
					if (exists) {
						return stringRedisTemplate.expire(tokenKey, timeout, TimeUnit.MINUTES);
					}
					return false;
				} catch (UnsupportedEncodingException e) {
					log.error("Occur an unsupported encoding exception when trying to encode the token key!", e);
					return false;
				}
			}
		});

	}

	@Override
	public void ivc(String sid, HttpServletResponse httpResponse) {
		ByteArrayOutputStream out = null;
		try {
			out = new ByteArrayOutputStream();

			String token = TokenSecurity.getInstunce().decrypt(sid);
			final String vcKey = GorgeousParameters.IVC_PREFIX + token;
			final String vcValue = IVC.getInstunce().build(out, GorgeousParameters.IVC_WIDTH, GorgeousParameters.IVC_HEIGHT);

			this.stringRedisTemplate.opsForValue().set(vcKey, vcValue, 300, TimeUnit.SECONDS);

			httpResponse.reset();
			httpResponse.setHeader("Pragma", "No-cache");
			httpResponse.setHeader("Cache-Control", "no-cache");
			httpResponse.setDateHeader("Expires", 0);
			httpResponse.setContentType("image/jpeg");
			httpResponse.getOutputStream().write(out.toByteArray());
			httpResponse.getOutputStream().flush();
		} catch (Exception e) {
			log.error("验证码获取失败。", e);
			throw new RuntimeException(e);
		} finally {
			try {
				out.flush();
				out.close();
			} catch (Exception e) {
				log.error("关闭输出流异常。", e);
			}
		}
	}

	@Override
	public void signout(String sid) {
		String token = TokenSecurity.getInstunce().decrypt(sid);
		String key = GorgeousParameters.SESSION_ID_PREFIX + token;
		this.stringRedisTemplate.delete(key);
	}

	@Override
	public GeneralUserVo getCurrentUser(String sid) {
		String token = TokenSecurity.getInstunce().decrypt(sid);
		final String tokenKey = GorgeousParameters.SESSION_ID_PREFIX + token;
		String jsonText = this.stringRedisTemplate.opsForValue().get(tokenKey);
		if (jsonText != null && !jsonText.equals("")) {
			JSONObject json = JSON.parseObject(jsonText);
			return JSON.toJavaObject(json, GeneralUserVo.class);
		}
		return null;
	}

	@Override
	public AppLogResponse log(AppLogRequest request) {
		AppLogResponse response = new AppLogResponse();
		try {
			final String accessLogControlKey = GorgeousParameters.ACCESS_LOG_PREFIX + request.getAppCode() + ":" + request.getClientIp() + ":" + request.getUserCode() + ":" + request.getOpresuri();
			long accessLogTimes = this.stringRedisTemplate.opsForValue().increment(accessLogControlKey, 1);
			if (accessLogTimes == 1) {
				this.stringRedisTemplate.expire(accessLogControlKey, GorgeousParameters.ACCESS_LOG_CONTROL_MINUTES, TimeUnit.MINUTES);
			}
			if (accessLogTimes <= GorgeousParameters.SAME_ACCESS_MAX_LOG_TIMES) {
				AppLogEntity appLog = new AppLogEntity();
				appLog.setAppCode(request.getAppCode());
				appLog.setUserId(request.getUserId());
				appLog.setUserCode(request.getUserCode());
				appLog.setUserName(request.getUserName());
				appLog.setClientIp(request.getClientIp());
				appLog.setOpresuri(request.getOpresuri());
				appLog.setOptime(Calendar.getInstance().getTime());
				this.appLogRepository.save(appLog);
			}
		} catch (Exception e) {
			log.error("日志记录异常。", e);
		}
		return response;
	}
}